#include "fabric.h"

//base constructor
Fabric::Fabric(unsigned int _id) : id(_id)
{
    selected = false;
    hasTexture = false;
    initialState = currentState = State();

    ho = 2050; //kg/m^3
    thickness = 0.0001; //m   -   espessura do tecido
    recalculateParticlesMasses();
    recalculateParticlesKd();
    resetSpringsConstants();
}

//constructor for the playback file
Fabric::Fabric(unsigned int _id, std::vector<Particle *> _m, std::vector<Spring *> _s, std::vector<Face *> _f, bool _ht)
    : id(_id), hasTexture(_ht), particles(_m), springs(_s), faces(_f)
{

    selected = false;

    ho = 2050; //kg/m^3
    thickness = 0.0001; //m   -   espessura do tecido

    unsigned int dof = 0, size = _m.size();
    Matrix reference(size, 3);
    for(unsigned int i = 0; i < size; i++)
        for(unsigned int j = 0; j < 3; j++)
            reference[i][j] = dof++;

    initialState = currentState = State(0.0, dof, reference);
}

//constructor for the file
Fabric::Fabric(unsigned int _id, std::vector<Particle *> _m, std::vector<Spring *> _s, std::vector<Face *> _f, unsigned int _dof, const Matrix &_ref, bool _ht)
    : id(_id), hasTexture(_ht), particles(_m), springs(_s), faces(_f)
{
    selected = false;

    currentState = State(0.0, _dof, _ref);

    currentState.setPosition(populatePosition());

    initialState = currentState;

    ho = 2050; //kg/m^3
    thickness = 0.0001; //m   -   espessura do tecido
    recalculateParticlesMasses();
    recalculateParticlesKd();
    resetSpringsConstants();
}

Fabric::~Fabric()
{
    for(unsigned int i = 0; i < particles.size(); i++)
    {
        delete particles[i];
    }
    particles.clear();

    for(unsigned int i = 0; i < springs.size(); i++)
    {
        delete springs[i];
    }
    springs.clear();

    for(unsigned int i = 0; i < faces.size(); i++)
    {
        delete faces[i];
    }
    springs.clear();
}

void Fabric::recalculateParticlesKd(double _defaultKd)
{
	for(unsigned int i = 0; i < particles.size(); i++)
	{
	    Particle *p = particles[i];

	    double area = p->area();

	    p->setKd(area * _defaultKd);
	}
}

void Fabric::recalculateParticlesMasses()
{
	for(unsigned int i = 0; i < particles.size(); i++)
	{
	    Particle *p = particles[i];

	    double area = p->area();

        p->setMass(area * thickness * ho);
	}

    double peso = 0.0;
    for(unsigned int i = 0; i < particles.size(); i++)
    {
        peso += particles.at(i)->getMass() ;
    }
	std::cout << "Massa total do tecido " << peso << " ." << std::endl;
}

void Fabric::resetSpringsConstants(double _ksStructural, double _kdStructural, double _ksShear, double _kdShear, double _ksFlexion, double _kdFlexion)
{
	for(unsigned int i = 0; i < springs.size(); i++)
	{
	    Spring *s = springs[i];

        switch(s->getType())
        {
            case FLEXION:
            {
                s->setKsAndKd(_ksFlexion, _kdFlexion);
                break;
            }
            case SHEAR:
            {
                s->setKsAndKd(_ksShear, _kdShear);
                break;
            }
            default:
            {
                s->setKsAndKd(_ksStructural, _kdStructural);
                break;
            }
        }
	}
}

//return the current State
const State &Fabric::getState()
{
    return currentState;
}
//change the current state
void Fabric::setState(const State &_s)
{
    currentState = _s;
}

void Fabric::updateState()
{
    unsigned int size = particles.size();
    Vector position(size * 3);
    Vector velocity(size * 3);
    for(unsigned int i = 0; i < size; i++)
    {
        Particle *p = particles[i];
        Vector3D pos = p->getPosition();
        Vector3D vel = p->getVelocity();

        position[3*i] = pos.x;
        position[(3*i) + 1] = pos.y;
        position[(3*i) + 2] = pos.z;

        velocity[3*i] = vel.x;
        velocity[(3*i) + 1] = vel.y;
        velocity[(3*i) + 2] = vel.z;
    }
    currentState.setPositionAndVelocity(position, velocity);
}

//draw wireframe
void Fabric::drawWireframe()
{
    //------------- draw spring
    Spring *s;

    Particle *m;

    Vector3D pos;

    glDisable(GL_LIGHT0);
    glDisable(GL_LIGHTING);

    Color color;
    //------------- draw particle
    for(unsigned int i = 0; i < particles.size(); i++)
    {
        m = particles.at(i);


        glPointSize(2);
        if(m->isSelected()) glPointSize(4);


        if(selected)
        {
            glColor4f(1,0,0,1);
        }
        else
        {
            color = m->getColor();
            glColor4f(color.r, color.g, color.b, color.alpha);
        }

        pos = m->getPosition();

        glBegin(GL_POINTS);
            glVertex3f(pos.x, pos.y, pos.z);
        glEnd();

    }
    glPointSize(1);

    for(unsigned int i = 0; i < springs.size(); i++)
    {
        s = springs.at(i);
        // if(s->getType() != FLEXION) continue;

        glLineWidth(1);
        if(s->isSelected()) glLineWidth(3);

        color = s->getColor();
        glColor4f(color.r, color.g, color.b, color.alpha);

        glBegin(GL_LINES);
            m = particles.at(s->getParticleBegin());
            pos = m->getPosition();
            glVertex3f(pos.x, pos.y, pos.z);

            m = particles.at(s->getParticleEnd());
            pos = m->getPosition();
            glVertex3f(pos.x, pos.y, pos.z);
        glEnd();
    }
    glLineWidth(1);

    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
}

//draw fill
void Fabric::drawFill(bool _showNormal, bool _drawSmooth)
{
    Face *f;

    Vector3D normal, v, center;

    Color color;

    Particle *p;

    unsigned int size;

    std::vector<Vector3D> normals;

    if(_drawSmooth)
    {
//        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

        for(unsigned int i = 0; i < particles.size(); i++)
        { //para cada particula
            p = particles.at(i);

            normals.push_back( currentState.getParticleNormal(p) );
        }

        if(hasTexture) glEnable(GL_TEXTURE_2D);
        else std::cout <<"Não vai desenhar textura" <<std::endl;

        for(unsigned int i = 0; i < faces.size(); i++)
        {
            f = faces.at(i);

            center = Vector3D(0.0, 0.0, 0.0);

            size = f->getTotalVertices();

            double textureU = 0, textureV = 0;


            glBegin(GL_TRIANGLE_STRIP);
            for(unsigned int j = 0; j < size; j++)
            {
                p = f->getParticleI(j);

                v = p->getPosition();

                center = center + v;

                if(selected)
                {
                    glColor4f(1,0,0,1);
                }
                else
                {
                    if(hasTexture)
                    {
                        textureU = p->getTextureCoordU();
                        textureV = p->getTextureCoordV();
                    }
                    color = p->getColor();
                    glColor4d(color.r, color.g, color.b, color.alpha);
                }

                normal = normals.at(p->getID());
                glNormal3d(normal.x, normal.y, normal.z);
                if(hasTexture) glTexCoord2d(textureU, textureV);
                glVertex3d(v.x, v.y, v.z);

            }
            glEnd();

            if(_showNormal)
            {
                //desenhando normal do vertice
                glDisable(GL_LIGHT0);
                glDisable(GL_LIGHTING);

                glColor4f(1., 0.0, 0.0, 0.7);
                glLineWidth(2);
                glBegin(GL_LINES);
                for(unsigned int j = 0; j < size; j++)
                {
                    p = f->getParticleI(j);

                    v = p->getPosition();

                    normal = normals.at(p->getID());

                    center = v + (normal * (1.0/10.0));

                    glVertex3f(v.x, v.y, v.z);
                    glVertex3f(center.x, center.y, center.z);
                }
                glEnd();
                glLineWidth(1);

                glEnable(GL_LIGHT0);
                glEnable(GL_LIGHTING);
            }
        }

        if(hasTexture) glDisable(GL_TEXTURE_2D);

    }
    else
    {
        for(unsigned int i = 0; i < faces.size(); i++)
        {
            f = faces.at(i);
            normal = f->getNormal();
            glNormal3d(normal.x, normal.y, normal.z);

            center = Vector3D(0.0, 0.0, 0.0);

            size = f->getTotalVertices();
            glBegin(GL_TRIANGLE_STRIP);
            for(unsigned int j = 0; j < size; j++)
            {
                p = f->getParticleI(j);

                v = p->getPosition();

                center = center + v;

                if(selected)
                {
                    glColor4f(1,0,0,1);
                }
                else
                {
                    color = p->getColor();
                    glColor4d(color.r, color.g, color.b, color.alpha);
                }

                glVertex3d(v.x, v.y, v.z);
            }
            glEnd();

            if(_showNormal)
            {
                //desenhando normal da face
                normal = Vector3D(Vector(normal).normalize()) * (1.0/10.0);
                center = center * (1.0/size);

                glDisable(GL_LIGHT0);
                glDisable(GL_LIGHTING);

                glLineWidth(2);
                glBegin(GL_LINES);
                    glColor4f(1., 0.0, 0.0, 0.7);
                    glVertex3dv(center.intoVector());
                    glVertex3dv( ( normal+center).intoVector() );
                glEnd();
                glLineWidth(1);

                glEnable(GL_LIGHT0);
                glEnable(GL_LIGHTING);
            }
        }
    }
}

//choosing between wirefram and draw fill
void Fabric::draw(bool _wireframe, bool _showNormal, bool _drawSmooth)
{
	glPushMatrix();

    if(_wireframe || faces.empty())
    {      //no caso o desenho das massas e molas
        drawWireframe();
    }
    else
    {
        drawFill(_showNormal, _drawSmooth);
    }

	glPopMatrix();
}

//adds particle
void Fabric::addParticle(Particle *_m)
{
    particles.push_back(_m);
}

//return the particle in the position i
Particle *Fabric::getParticle(int _i) const
{
    return particles[_i];
}

//return the array of particles
const std::vector<Particle *> &Fabric::getParticles() const
{
    return particles;
}

//return the size of vector of particle
unsigned int Fabric::getParticlesSize() const
{
    return particles.size();
}

//add spring
void Fabric::addSpring(Spring *_s)
{
    springs.push_back(_s);
}

//return spring in the position i
Spring *Fabric::getSpring(int _i) const
{
    return springs[_i];
}

//return the array of springs
const std::vector<Spring*> &Fabric::getSprings() const
{
    return springs;
}

//return the size of vector of springs
unsigned int Fabric::getSpringsSize() const
{
    return springs.size();
}
// populate the vector position
Vector Fabric::populatePosition()
{
    Vector result(currentState.getDOF());
    const Matrix &ref = currentState.getReferenceMatrix();

    Vector3D p;
    for(unsigned int i = 0; i < particles.size(); i++)
    {
        p = particles.at(i)->getPosition();
        if(ref[i][0] != -1) result[ref[i][0]] = p.x;
        if(ref[i][1] != -1) result[ref[i][1]] = p.y;
        if(ref[i][2] != -1) result[ref[i][2]] = p.z;
    }
    return result;
}

//update springs length
void Fabric::updateSpringsLength()
{
    Spring *s;

    for (unsigned int i = 0; i < springs.size(); i++)
    {
        s = springs.at(i);
        s->getCurrentLength(particles.at( s->getParticleBegin() ),  particles.at( s->getParticleEnd() ));
    }
}

const State &Fabric::restart()
{
    currentState = initialState;
    for(unsigned int i = 0; i < particles.size(); i++)
    {
        particles.at(i)->reset();
    }

    for(unsigned int i = 0; i < springs.size(); i++)
    {
        springs.at(i)->relax();
    }
    return initialState;
}

QString Fabric::getSpringsFaces()
{
    QString str = "";

    str.append( QString("# molas ( s tipo particula1 particula2 )\n") );
    for(unsigned int i = 0; i < springs.size(); i++)
    {
        Spring *s = springs.at(i);
        str.append( QString("s ") );
        str.append( QString::number(s->getType()) );
        str.append( QString(" ") );
        str.append( QString::number(s->getParticleBegin()) );
        str.append( QString(" ") );
        str.append( QString::number(s->getParticleEnd()) );
        str.append( QString("\n") );
    }

    str.append( QString("# faces ( f particula1 particula2 particula3 )\n") );
    for(unsigned int i = 0; i < faces.size(); i++)
    {
        Face *f = faces.at(i);
        str.append( QString("f") );
        for(unsigned int j = 0; j < f->getTotalVertices(); j++){
            str.append( QString(" ") );
            str.append( QString::number(f->getParticleI(j)->getID()) );
        }
        str.append( QString("\n") );
    }
    return str;
}

QString Fabric::getUV()
{
    QString str = "";

    str.append( QString("# textura ( x u v )\n") );
    for(unsigned int i = 0; i < particles.size(); i++)
    {
        Particle *p = particles.at(i);
        str.append( QString("x ") );
        str.append( QString::number(p->getTextureCoordU()) );
        str.append( QString(" ") );
        str.append( QString::number(p->getTextureCoordV()) );
        str.append( QString("\n") );
    }

    return str;
}

QString Fabric::getInitialState()
{
    QString str = " ";

    str.append( QString::number(currentState.getT()) );

    for(unsigned int i = 0; i < particles.size(); i++)
    {
        Vector3D v = particles.at(i)->getPosition();
        str.append( QString(" ") );
        str.append( QString::number(v.x) );
        str.append( QString(" ") );
        str.append( QString::number(v.y) );
        str.append( QString(" ") );
        str.append( QString::number(v.z) );
    }
    str.append( QString("\n") );

    return str;
}

void Fabric::setParticleState(unsigned int _i, bool _x, bool _y, bool _z, const Vector3D &_p, const Vector3D &_v)
{
    currentState.setParticleState(_i, _x, _y, _z, _p, _v);
    initialState.setParticleState(_i, _x, _y, _z, _p, _v);
}
